실행컨텍스트와 this binding

함수는 내/외부 Scope 이외에, this 라는 Scope를 가질 수 있다.

This Binding

실행컨텍스트의 this 바인딩 컴포넌트에 Scope가 바인딩된다.

{
  "실행컨텍스트_EC": {
    "렉시컬환경컴포넌트_LEC": {},
    "변수환경컴포넌트_VEC": {},
    "This바인딩컴포넌트_TBC": {}
  }
}

실행컨텍스트에 설정되는 변수환경컴포넌트와 렉시컬환경 컴포넌트와는 달리,
this 바인딩 컴포넌트는 호출 시점에 결정된다., 즉 동적 바인딩이다.

함수 호출 시점의 '주체'가 this로 바인딩 된다.
동적 바인딩되는 만큼 call(), apply(), bind()를 통해, 바인딩되는 객체를 변경할 수 있다.

아래 예제들을 보자

window와 this

function test() {
  return this;
}
console.log(this === window); //=> true

우리는 글로벌에 선언한 함수에 대해선, get()을 그냥 호출하지, window.get()이라고 호출하지 않는다. 묵시적으로 글로벌 오브젝트에서 호출하는 것으로 되어있기 때문이다.

Callback과 this

const counter = {
  value: 0,
  incNormal: function () {
    setTimeout(function () {
      console.log('normal:', this.value);
    }, 100);
  },
};

counter.incNormal(); // normal: undefined (this → window)

counter가 this가 되는 것이 아닌가? 왜 undefined일까? this는 호출 주체로 결정되기 때문이다.

counter.incNormal()을 호출하면 incNormal의 this는 counter가 맞다.
하지만 console을 찍는 callback함수는 window가 호출하기 때문에,
setTimeout의 callback 함수의 this는 window가 된다.
그래서 undefined가 출력된다.

arrow function의 this binding

일반 함수의 this는 호출 시점에 결정되지만, arrow function은 선언 시점의 Scope로 미리 할당된다.

const counter = {
  value: 0,
  incNormal: function () {
    setTimeout(function () {
      console.log('normal:', this.value);
    }, 100);
  },
  incArrow: function () {
    setTimeout(() => {
      console.log('arrow :', this.value);
    }, 100);
  },
};

counter.incNormal(); // normal: undefined (this → window)
counter.incArrow(); // arrow : 0 (this → counter)

함수 선언문은 호출 시점의 주체가 this가 된다고 했다.
그래서 여전히 undefined가 출력된다.

하지만 arrow function의 this는 counter 객체로 고정되어 있기 때문에
0이 출력되는 것이다.

Prototype과 this

인스턴스의 목적은, 인스턴스마다 고유의 값을 유지하는데 있다. 인스턴스에서 this는 인스턴스를 참조하여, 인스턴스의 프로퍼티에 접근하게끔 만들어졌다.

var book = {};
book.Point = function (point) {
  this.point = point;
};
book.Point.prototype.getPoint = function () {
  console.log(this.point);
};

var obj = new book.Point(100);
obj.getPoint(); //=> 100

var obj2 = new book.Point(200);
obj2.getPoint(); //=> 200

//메소드 변경
book.Point.prototype.getPoint = function () {
  console.log('changed');
};
obj.getPoint(); //=> changed
obj2.getPoint(); //=> changed

this는 인스턴스별 독립적인 공간을 만드는데 쓰이고
prototype은 인스턴스들을 공통적으로 다루는데 쓰인다.


call, apply, bind

이 메소드를 통해 this로 참조할 오브젝트를 변경하거나 고정할 수 있다.

const test = {
  value: 10,
  get(param) {
    return param + this.value;
  },
};

get(10); //=> 20 반환
  • test.get.call({ value: 20 }, 10) :
    • value가 20인 객체로 this를 바인딩한 후에 바로 출력한다.
    • 그래서 출력은 30이다.
    • 딱 호출할 때만 바인딩을 변경하는 것
  • test.get.apply({ value: 20 }, [10]) :
    • call과 똑같지만 인자를 배열로 넘겨야한다.
  • bind
    • bind는 this를 바인딩한 새로운 함수를 반환한다. 그래서 영구적이다.
    const newFunc = test.get.bind({ value: 20 });
    newFunc(20); //=> 출력 40